#ifndef SINGLETON_H
#define SIGNLETON_H

#ifndef MY_DESIGN_PATTERN_TEMPLATE_NAMESPACE_BEGIN
#define MY_DESIGN_PATTERN_TEMPLATE_NAMESPACE_BEGIN namespace dpt {
#endif // !MY_DESIGN_PATTERN_TEMPLATE_NAMESPACE_BEGIN

#ifndef MY_DESIGN_PATTERN_TEMPLATE_NAMESPACE_END
#define MY_DESIGN_PATTERN_TEMPLATE_NAMESPACE_END }
#endif // !MY_DESIGN_PATTERN_TEMPLATE_NAMESPACE_END

#include <cstdint>
#include <stdexcept>
#include <mutex>

MY_DESIGN_PATTERN_TEMPLATE_NAMESPACE_BEGIN
template<class T>
class HungrySingleton
{
private:
	static T* instance;
public:
	static T* getInstance() 
	{ 
		if (dpt::HungrySingleton<T>::instance == nullptr)
		{
			throw std::logic_error("The hungry singleton instance has become \"nullptr\"!");
		}
		return dpt::HungrySingleton<T>::instance; 
	}
	static void destroyInstance() 
	{ 
		delete dpt::HungrySingleton<T>::instance;
		dpt::HungrySingleton<T>::instance = nullptr;
	}
};

template<class T>
T* dpt::HungrySingleton<T>::instance = new T();

template<class T>
class LazySingleton
{
private:
	static T* instance;
	static std::mutex mtx;
public:
	static T* getInstance()
	{
		if (dpt::LazySingleton<T>::instance == nullptr)
		{
			std::lock_guard<std::mutex> lock(dpt::LazySingleton<T>::mtx);
			if (dpt::LazySingleton<T>::instance == nullptr)
			{
				dpt::LazySingleton<T>::instance = new T();
			}
		}
		return dpt::LazySingleton<T>::instance;
	}
	static void destroyInstance()
	{
		std::lock_guard<std::mutex> lock(dpt::LazySingleton<T>::mtx);
		delete dpt::LazySingleton<T>::instance;
		dpt::LazySingleton<T>::instance = nullptr;
	}
};

template<class T>
T* dpt::LazySingleton<T>::instance = nullptr;

template<class T>
std::mutex dpt::LazySingleton<T>::mtx;

template<class T, std::int32_t Type = 0>
class MultipleSingleton
{
private:
	static T* instance;
	static std::int32_t type;
	static std::mutex mtx;
public:
	static T* getInstance()
	{
		if (dpt::MultipleSingleton<T, Type>::instance == nullptr)
		{
			std::lock_guard<std::mutex> lock(dpt::MultipleSingleton<T, Type>::mtx);
			if (dpt::MultipleSingleton<T, Type>::instance == nullptr)
			{
				dpt::MultipleSingleton<T, Type>::instance = new T();
			}
		}
		return dpt::MultipleSingleton<T, Type>::instance;
	}
	static void destroyInstance()
	{
		std::lock_guard<std::mutex> lock(dpt::MultipleSingleton<T, Type>::mtx);
		delete dpt::MultipleSingleton<T, Type>::instance;
		dpt::MultipleSingleton<T, Type>::instance = nullptr;
	}
	static std::int32_t getType()
	{
		return dpt::MultipleSingleton<T, Type>::type;
	}
};

template<class T, std::int32_t Type>
T* dpt::MultipleSingleton<T, Type>::instance = nullptr;

template<class T, std::int32_t Type>
std::int32_t dpt::MultipleSingleton<T, Type>::type = Type;

template<class T, std::int32_t Type>
std::mutex dpt::MultipleSingleton<T, Type>::mtx;
MY_DESIGN_PATTERN_TEMPLATE_NAMESPACE_END

#endif // !SINGLETON_H
